<template>
  <div id="global-uploader" :class="{'global-uploader-single': !global}">
    <uploader
      ref="uploader"
      :options="options"
      :fileStatusText="fileStatusText"
      :autoStart="false"
      @file-added="filesAdded"
      @file-success="fileSuccess"
      @file-error="onFileError"
      class="uploader-app"
    >
      <uploader-unsupport></uploader-unsupport>
      <uploader-btn id="global-uploader-btn" ref="uploadBtn">选择文件</uploader-btn>
      <uploader-list v-show="panelShow">
        <div class="file-panel" slot-scope="props" :class="{ collapse: collapse }">
          <div class="file-title">
            <div class="title">文件列表</div>
            <div class="operate">
              <el-button
                @click="collapse = !collapse"
                type="text"
                :title="collapse ? '展开' : '折叠'"
              >
                <i class="iconfont" :class="collapse ? 'el-icon-full-screen' : 'el-icon-minus'"></i>
              </el-button>
              <el-button @click="close" type="text" title="关闭">
                <i class="el-icon-close"></i>
              </el-button>
            </div>
          </div>

          <ul class="file-list">
            <li
              class="file-item"
              v-for="file in props.fileList"
              :key="file.id"
            >
              <uploader-file
                :class="['file_' + file.id, customStatus]"
                ref="files"
                :file="file"
                :list="true"
              ></uploader-file>
            </li>
            <div class="no-file" v-if="!props.fileList.length">
              <i class="iconfont icon-empty-file"></i> 暂无待上传文件
            </div>
          </ul>
        </div>
      </uploader-list>
    </uploader>
  </div>
</template>

<script>
import { getToken } from '@/utils/auth'
import SparkMD5 from 'spark-md5'
import { simpleUpload } from '@/api/system/simpleUpload'
import Bus from '@/utils/bus'

export default {
  name: 'globalUploader',
  props: {
    global: {
      type: Boolean,
      default: true
    },
  },
  data() {
    return {
      options: {
        target: process.env.VUE_APP_BASE_API + '/simpleUpload/chunk',
        // 开启服务端分片校验功能
        chunkSize: 20 * 1024 * 1024,
        testChunks: true,
        //剩余时间
        parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
          return parsedTimeRemaining
            .replace(/\syears?/, '年')
            .replace(/\days?/, '天')
            .replace(/\shours?/, '小时')
            .replace(/\sminutes?/, '分钟')
            .replace(/\sseconds?/, '秒')
        },
        // 服务器分片校验函数
        checkChunkUploadedByResponse: (chunk, message) => {
          const result = JSON.parse(message)
          if (result.data.skipUpload) {
            //秒传
            this.skip = true
            this.$message({
              message: '上传成功',
              type: 'success'
            })
            return true
          }
          chunk.file.objectName = result.data.objectName;
          chunk.file.uploadId = result.data.uploadId;
          return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0
        },
        headers: {
          Authorization: 'Bearer ' + getToken()
        },
        processParams: (params, file) => {
          return {
            ...params,
            objectName: file.objectName,
            uploadId: file.uploadId
          };
        }
      },
      attrs: {
        accept: 'image/*'
      },
      fileStatusText: {
        success: '上传成功',
        error: '上传失败',
        uploading: '上传中',
        paused: '已暂停',
        waiting: '等待上传'
      },
      skip: false,
      panelShow: false, //选择文件后，展示上传panel
      collapse: false,
      customStatus: ''
    }
  },
  mounted() {
    // 监听打开上传面板事件
    Bus.$on('openUploader', () => {
      if (this.$refs.uploadBtn) {
        // 点击上传按钮
        this.$refs.uploadBtn.$el.click()
      }
    })
  },
  computed: {
    // 获取uploader实例
    uploader() {
      return this.$refs.uploader.uploader
    }
  },
  methods: {
    /**
     * 文件添加
     * @param file
     * @param event
     */
    filesAdded(file) {
      this.panelShow = true
      file.objectName = null;
      file.uploadId = null;
      // 计算MD5
      this.computeMD5(file).then((result) => this.startUpload(result))
    },
    /**
     * 计算MD5
     * @param file
     */
    computeMD5(file) {
      let fileReader = new FileReader()
      let time = new Date().getTime()
      let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      let currentChunk = 0
      //分片大小
      const chunkSize = this.options.chunkSize
      //分片数
      let chunks = Math.ceil(file.size / chunkSize)
      //追加数组缓冲区。
      let spark = new SparkMD5.ArrayBuffer()
      // 文件状态设为"计算MD5"
      this.statusSet(file.id, 'md5')
      file.pause()
      loadNext()
      // 读取文件
      return new Promise((resolve, reject) => {
        fileReader.onload = (e) => {
          spark.append(e.target.result)
          if (currentChunk < chunks) {
            currentChunk++
            loadNext()
            // 实时展示MD5的计算进度
            this.$nextTick(() => {
              const md5ProgressText = '校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%'
              document.querySelector(`.custom-status-${file.id}`).innerText = md5ProgressText
            })
          } else {
            let md5 = spark.end()
            spark.destroy() //释放缓存
            // md5计算完毕
            resolve({ md5, file })
            console.log(
              `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${
                new Date().getTime() - time
              } ms`
            )
          }
        }
        // 读取文件出错
        fileReader.onerror = function() {
          this.$message.error(`文件${file.name}读取出错，请检查该文件`)
          file.cancel()
          reject()
        }
      })

      //读取方法
      function loadNext() {
        let start = currentChunk * chunkSize
        let end = start + chunkSize >= file.size ? file.size : start + chunkSize
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }
    },
    // md5计算完毕，开始上传
    startUpload({ md5, file }) {
      file.uniqueIdentifier = md5
      file.resume()
      this.statusRemove(file.id)
    },
    /**
     * 上传成功
     * @param rootFile
     * @param file
     * @param response
     * @param chunk
     */
    fileSuccess(rootFile, file, response, chunk) {
      const result = JSON.parse(response)
      if (result.code == 200 && !this.skip) {
        // 文件状态设为“合并中”
        this.statusSet(file.id, 'merging')
        simpleUpload({ md5: file.uniqueIdentifier }).then(resp => {
          if (resp.code == 200) {
            // 文件合并成功
            this.emit('fileSuccess')
            this.statusRemove(file.id)
            this.$message({
              message: '上传成功',
              type: 'success'
            })
          }
        }).catch(e => {
          // 文件状态设为“失败”
          this.statusSet(file.id, 'failed')
          return;
        })
      }
      if (this.skip) {
        this.skip = false
      }
    },
    /**
     * 关闭上传面板
     */
    close() {
      this.uploader.cancel()
      this.panelShow = false
    },
    /**
     * 触发事件
     * @param e
     */
    emit(e) {
      Bus.$emit(e)
      this.$emit(e)
    },

    /**
     * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    statusSet(id, status) {
      let statusMap = {
        md5: {
          text: '校验MD5',
          bgc: '#fff'
        },
        merging: {
          text: '合并中',
          bgc: '#e2eeff'
        },
        transcoding: {
          text: '转码中',
          bgc: '#e2eeff'
        },
        failed: {
          text: '上传失败',
          bgc: '#e2eeff'
        }
      }
      this.customStatus = status
      this.$nextTick(() => {
        const statusTag = document.createElement('p')
        statusTag.className = `custom-status-${id} custom-status`
        statusTag.innerText = statusMap[status].text
        statusTag.style.backgroundColor = statusMap[status].bgc

        const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
        statusWrap.appendChild(statusTag)
      })
    },
    onFileError(rootFile, file, response, chunk) {
      this.error(response)
    },
    /**
     * 移除自定义的状态
     * @param id
     */
    statusRemove(id) {
      this.customStatus = ''
      this.$nextTick(() => {
        const statusTag = document.querySelector(`.custom-status-${id}`)
        statusTag.remove()
      })
    }
  }
}
</script>

<style lang="scss">
#global-uploader {
  &:not(.global-uploader-single) {
    position: fixed;
    z-index: 20;
    right: 15px;
    bottom: 15px;
    box-sizing: border-box;
  }

  .uploader-app {
    width: 520px;
  }

  .file-panel {
    background-color: #fff;
    border: 1px solid #e2e2e2;
    border-radius: 7px 7px 0 0;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);

    .file-title {
      display: flex;
      height: 40px;
      line-height: 40px;
      padding: 0 15px;
      border-bottom: 1px solid #ddd;

      .operate {
        flex: 1;
        text-align: right;

        i {
          font-size: 18px;
        }
      }
    }

    .file-list {
      position: relative;
      height: 240px;
      overflow-x: hidden;
      overflow-y: auto;
      background-color: #fff;
      transition: all 0.3s;
      list-style: none;
      padding-left: 0;

      .file-item {
        background-color: #fff;
      }
    }

    &.collapse {
      .file-title {
        background-color: #e7ecf2;
      }

      .file-list {
        height: 0;
      }
    }
  }

  .no-file {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 16px;
  }

  .uploader-file {
    &.md5 {
      .uploader-file-resume {
        display: none;
      }
    }
  }

  .uploader-file-icon {
    &:before {
      content: '' !important;
    }

    &[icon='image'] {
      background: url(~@/assets/images/image-icon.png);
    }

    &[icon=audio] {
      background: url(~@/assets/images/audio-icon.png);
      background-size: contain;
    }

    &[icon='video'] {
      background: url(~@/assets/images/video-icon.png);
    }

    &[icon='document'] {
      background: url(~@/assets/images/text-icon.png);
    }

    &[icon=unknown] {
      background: url(~@/assets/images/zip.png) no-repeat center;
      background-size: contain;
    }
  }

  .uploader-file-actions > span {
    margin-right: 6px;
  }

  .custom-status {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    background-color: rgba(255, 255, 255, 0.8);
    pointer-events: none;
  }
}

/* 隐藏上传按钮 */
#global-uploader-btn {
  position: absolute;
  clip: rect(0, 0, 0, 0);
}

.global-uploader-single {
  #global-uploader-btn {
    position: relative;
  }
}
</style>

